//
// Created by User on 24-10-3.
//

#include "general_decoder.h"
#include "media_packet.h"
#include <glog/logging.h>

extern "C" {
#include <libavcodec/avcodec.h>
}

namespace decoder
{
    general_decoder::general_decoder(interfaces::packet_provider& packet_provider)
        : m_provider(packet_provider)
    {
        try
        {
            this->init_decoder();
        }
        catch (error::decoder_error& error)
        {
            this->clean_decoder(error.get_type());
            throw;
        }
        catch (...)
        {
            this->clean_decoder(error::decoder_error::type::UNKNOWN_ERROR);
            throw;
        }
    }

    general_decoder::~general_decoder()
    {
        this->clean_decoder(error::decoder_error::type::UNKNOWN_ERROR);
    }

    utils::media_frame general_decoder::get_frame()
    {
        if (this->is_end && this->m_que.empty())
        {
            return {};
        }
        if (!this->is_end && this->m_que.size() < 5)
        {
            this->run_decode();
        }
        auto temp = this->m_que.front();
        this->m_que.pop();
        return temp;
    }

    const AVCodecParameters* general_decoder::get_codec() const
    {
        if (!this->m_codec_p)
        {
            this->m_codec_p = avcodec_parameters_alloc();
            if (this->m_codec_p == nullptr)
            {
                throw error::decoder_error(error::decoder_error::type::PARAMETERS_ALLOC_ERROR,
                                           "can not alloc parameters");
            }
            int ret = avcodec_parameters_from_context(this->m_codec_p, this->m_code_ctx);
            if (ret < 0)
            {
                throw error::decoder_error(error::decoder_error::type::PARAMETERS_SET_ERROR, ret);
            }
        }
        return this->m_codec_p;
    }

    AVRational general_decoder::get_timebase() const
    {
        return this->m_provider.get_timebase();
    }

    int general_decoder::total_frame() const
    {
        return this->m_provider.total_frames();
    }

    void general_decoder::init_decoder()
    {
        this->m_codec = this->get_decoder();
        if (!this->m_codec)
        {
            throw error::decoder_error(error::decoder_error::type::DECODER_NOT_FOUND, "can not found decoder");
        }
        LOG(INFO) << "find decoder : " << this->m_codec->long_name;

        this->m_code_ctx = avcodec_alloc_context3(this->m_codec);
        if (!this->m_code_ctx)
        {
            throw error::decoder_error(error::decoder_error::type::CODEC_CTX_ALLOC_ERROR, "can not alloc decoder ctx");
        }

        int ret = avcodec_parameters_to_context(this->m_code_ctx, this->m_provider.get_codec());
        if (ret < 0)
        {
            throw error::decoder_error(error::decoder_error::type::CODEC_CTX_SET_ERROR, ret);
        }

        ret = avcodec_open2(this->m_code_ctx, this->m_codec, nullptr);
        if (ret < 0)
        {
            throw error::decoder_error(error::decoder_error::type::CODEC_OPEN_ERROR, ret);
        }

        this->m_frame = av_frame_alloc();
        if (!this->m_frame)
        {
            throw error::decoder_error(error::decoder_error::type::FRAME_ALLOC_ERROR, "can not alloc frame");
        }
    }

    void general_decoder::clean_decoder(error::decoder_error::type _type)
    {
        switch (_type)
        {
        case error::decoder_error::type::UNKNOWN_ERROR:
            av_frame_free(&this->m_frame);
        case error::decoder_error::type::FRAME_ALLOC_ERROR: ;
        case error::decoder_error::type::CODEC_OPEN_ERROR: ;
        case error::decoder_error::type::CODEC_CTX_SET_ERROR:
            avcodec_free_context(&this->m_code_ctx);
        case error::decoder_error::type::CODEC_CTX_ALLOC_ERROR: ;
        case error::decoder_error::type::DECODER_NOT_FOUND: ;
        }

        if (this->m_codec_p)
        {
            avcodec_parameters_free(&this->m_codec_p);
        }
    }

    void general_decoder::run_decode()
    {
        while (!this->is_end && this->m_que.size() < 10)
        {
            auto pkt = this->m_provider.get_packet();
            if (!pkt)
            {
                this->is_end = true;
            }
            int ret = avcodec_send_packet(this->m_code_ctx, this->is_end ? nullptr : pkt.get_packet());
            if (ret < 0)
            {
                throw error::decoder_error(error::decoder_error::type::SEND_PACKET_ERROR, ret);
            }
            while (ret >= 0)
            {
                ret = avcodec_receive_frame(this->m_code_ctx, this->m_frame);
                if (AVERROR_EOF == ret || AVERROR(EAGAIN) == ret)
                {
                    break;
                }
                if (ret < 0)
                {
                    throw error::decoder_error(error::decoder_error::type::RECEIVE_FRAME_ERROR, ret);
                }
                this->m_que.emplace(this->m_frame);
            }
        }
    }

    const AVCodec* general_decoder::get_decoder()
    {
        return avcodec_find_decoder(this->m_provider.get_codec()->codec_id);
    }
} // decoder
